AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )
include( 'shared.lua' )

local material 	= "cable/rope"

function ENT:SpawnFunction( ply, tr )
	if ( !tr.Hit ) then return end
	local SpawnPos = tr.HitPos + tr.HitNormal * 128
	local ent = ents.Create("sent_spaceanon_wormhole")
	ent:SetPos( SpawnPos )
	ent:Spawn()
	ent:Activate()
	return ent
end

function ENT:Initialize()
	self.Entity:SetModel( "models/Combine_Helicopter/helicopter_bomb01.mdl" )
	self.Entity:PhysicsInit(SOLID_VPHYSICS)
	self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
	self.Entity:SetSolid(SOLID_VPHYSICS)
	self.Entity:SetAngles(Angle(math.random(0,360),math.random(0,360),math.random(0,360)))
	local phys = self.Entity:GetPhysicsObject()
	if (phys:IsValid()) then
		phys:EnableMotion(false)
	end
	self.trails = {}
	self.apperture = nil
	timer.Simple(0.3, self.CreateApperture,self)
	self:ChangeVec()
	timer.Simple(0.3, self.CreateTrails,self)
	self:OpenApperture()
end

function ENT:CreateTrails()
	if not self.Entity then return end
	selfpos = self.Entity:GetPos()
	for i=1, 4 do
		local radi = math.random(48,128)
		local entpos = selfpos+Vector(math.random(-radi,radi),math.random(-radi,radi),math.random(-radi,radi))
		local ent = ents.Create("sent_spaceanon_wormhole_trail")
		ent:SetPos(entpos)
		ent:Spawn()
		table.insert(self.trails, ent)
		local LPos1 = ent:WorldToLocal(entpos)
		local LPos2 = self.Entity:WorldToLocal(selfpos)
		constraint.Rope(ent,self.Entity,0,0,Vector(0,0,0),Vector(0,0,0),radi,0,0,0,material,1)
	end
end

function ENT:ChangeVec()
	if not self.Entity then return end
	self.vec = VectorRand()
	timer.Simple(3, self.ChangeVec, self)
end

function ENT:CreateApperture()
	if not self.Entity then return end
	if self.apperture then return end
	local tmpmark = ents.Create("sent_spaceanon_wormhole")
	tmpmark:SetPos(Vector(0,0,0))
	tmpmark:Spawn()
	while true do
		tmpmark:SetPos(VectorRand()*16384)
		if tmpmark:IsInWorld() then
			tmpmark:SetApperture(self.Entity)
			self.apperture = tmpmark
			break
		end
	end
end

function ENT:SetApperture(ent)
	self.apperture = ent
end

function ENT:OnRemove()
	if self.apperture and self.apperture:IsValid() then
		self.apperture:Remove()
	end
	for _,v in pairs(self.trails) do	
		if v and v:IsValid() then
			v:Remove()
		end
	end
end

function ENT:CloseApperture()
	self.Open = false
	timer.Simple(15, self.OpenApperture,self)
	self.Entity:SetMaterial("models/debug/debugwhite")
	self.Entity:SetColor(0,0,0,255)
	local e = EffectData()
	e:SetOrigin(self.Entity:GetPos())
	e:SetNormal(self.vec)
	util.Effect("hypermass_refract_ring", e)
end

function ENT:OpenApperture()
	if not self.Entity then return end
	self.Open = true
	self.Entity:SetColor(255,255,255,255)
	self.Entity:SetMaterial("models/props_combine/stasisshield_sheet")
	local e = EffectData()
	e:SetOrigin(self.Entity:GetPos())
	e:SetNormal(self.vec)
	util.Effect("hypermass_refract_ring", e)
end

function ENT:Touch(ent)
	if not self.Open then return end
	local str = ent:GetClass()
	if (not string.find(str, "physgun_beam")) and
		(not string.find(str, "predicted_viewmodel")) and
		(not (string.find(str, "func_") and (str ~= "func_physbox"))) and
		(not string.find(str, "info_")) and
		(not string.find(str, "point_")) and
		(not string.find(str, "path_")) and
		(not string.find(str, "node")) and
		(not string.find(str, "sent_spaceanon")) and 
		(not string.find(str, "Environment")) and
		(not string.find(str, "environment")) and 		
		(not string.find(str, "env_")) then
		self:CloseApperture()
		self.apperture:CloseApperture()
		self:StartTeleport(ent) --Credits for the teleport code goes to aVoN the great.
	end
end

//aVoN and Tad2020 are to thank for making the following code possible

function ENT:StartTeleport(e)
	local attached = self:GetEntitiesForTeleport(e)
	if attached then
		local ents = self:PrepareTeleport(attached)
		self:Teleport(ents.Entity,ents.Entity.Entity)
		for _,v in pairs(ents.Attached) do
			self:Teleport(v,ents.Entity.Entity)
		end
	end
end

function ENT:Allowed(e,auto_close_check)
	local c = e:GetClass();
	local t = type(e):lower();
	local p = e:GetPhysicsObject();
	local moveable = true;
	if(p:IsValid() and t == "entity") then
		if(not p:IsMoveable()) then
			moveable = false;
		end
	end
	if(((t == "player" and not e:InVehicle()) or 
		(t == "entity" and (c:find("prop_[prv]") or (c:find("phys_") and not auto_close_check))) or -- Allow props and constraints
		t == "vehicle" or -- Vehicles
		(e.Type ~= nil and not c:find("stargate") and not (c:find("dhd") and auto_close_check)) or -- SENT's but not stargates - May cause into infinity selfteleportation - For more, watch this funny screeny ;)  http://forums.facepunchstudios.com/showpost.php?p=4747617&postcount=256
		t == "npc" or -- NPC's
		t == "weapon" or c == "npc_grenade_frag" or c == "rpg_missile" or c == "grenade_ar2" or c == "crossbow_bolt" or c == "npc_satchel" or c == "prop_combine_ball") and -- Weapons and grenades from weapons etc
		e:GetParent():EntIndex() == 0 -- Only allow unparented props to get teleported
		and (moveable or not auto_close_check) -- For the autoclose only - Is the object awake?
	) then  
		return true 
	end 
	return false 
end

function ENT:GetBones(e)
	local bones = {};
	if(type(e):lower() == "vehicle" or e:GetClass() == "prop_ragdoll") then
		for k=0,e:GetPhysicsObjectCount()-1 do
			local bone = e:GetPhysicsObjectNum(k);
			if(bone:IsValid()) then
				table.insert(bones,{
					Entity=bone,
					Position=e:WorldToLocal(bone:GetPos()),
					Velocity=e:WorldToLocal(e:GetPos()+bone:GetVelocity()),
				});
			end
		end
	end
	return bones;
end

function ENT:PrepareTeleport(tbl)
	local e = tbl.Entity;
	local g = {self.Entity,self.apperture}
	local a = {
		This=g[1]:GetAngles(),
		Other=g[2]:GetAngles(),
	}
	a.Delta=a.Other-a.This;
	local ret = {Attached={}};
	for _,v in pairs(tbl.Attached) do
		local vel = v:GetVelocity();
		local data = {
			Entity=v,
			Position={
				New=e:WorldToLocal(v:GetPos()),
				Old=v:GetPos(),
			},
			Velocity={
				New=e:WorldToLocal(vel+e:GetPos()),
				Old=vel,
			},
			Angles={
				Old=v:GetAngles(),
				New=v:GetAngles()+a.Delta,
				Delta=a.Delta,
			},
			Bones=self:GetBones(v),
		}
		table.insert(ret.Attached,data);
	end
	local vel = e:GetVelocity();
	ret.Entity={
		Entity=e;
		Position={
			New=g[2]:LocalToWorld(g[1]:WorldToLocal(e:GetPos())),
			Old=e:GetPos(),
		},
		Velocity={
			New=g[2]:LocalToWorld(g[1]:WorldToLocal(-1*vel+g[1]:GetPos())) - g[2]:GetPos(),
			Old=vel,
		},
		Angles={
			Old=e:GetAngles(),
			New=e:GetAngles()+a.Delta,
			Delta=a.Delta,
		},
		Bones=self:GetBones(e),
	}
	return ret;
end

function ENT:GetEntitiesForTeleport(e)
	if(self:Allowed(e)) then
		local entities = {};
		local attached = {};
		attached = {{},{}};
		GetAllConstrainedEntities(e,attached[1],attached[2]);
		for _,v in pairs(attached[1]) do
			if(v == self.Entity) then
				return false;
			end
		end
		local allow = true;
		for _,v in pairs(attached[1]) do
			if(v:GetClass() == "gmod_spawner") then
				allow = false;
				break;
			end
		end
		if(allow) then
			for _,v in pairs(attached[1]) do
				if(v:GetClass() ~= "gmod_spawner" and v ~= e and self:Allowed(v)) then
					table.insert(entities,v);
				end
			end
		end
		return {Entity=e,Attached=entities};
	else
		return false;
	end
end

function ENT:Teleport(tbl,base)
	local g = {self.Entity,self.apperture} -- Gates
	local p = tbl.Position;
	local b = tbl.Bones;
	local e = tbl.Entity;
	local a = tbl.Angles;
	local v = tbl.Velocity;
	local t = type(e):lower();
	if(e ~= base) then
		p.New = base:LocalToWorld(p.New);
		v.New = base:LocalToWorld(v.New)-base:GetPos();
	end
	v.New = math.RotationMatrixWH(g[2]:GetForward(),180,v.New);
	e.last_teleport = CurTime();
	if(e == base) then
		g[1]:SetCollisionGroup(COLLISION_GROUP_DEBRIS);
		g[2]:SetCollisionGroup(COLLISION_GROUP_DEBRIS);
		timer.Simple(0.8,g[1].SetCollisionGroup,g[1],0);
		timer.Simple(0.8,g[2].SetCollisionGroup,g[2],0);
	end
	if(t == "player") then
		local ai = e:GetAimVector();
		local pitch = math.deg(math.acos(ai.z))-90;
		ai.z=0; ai:Normalize();
		local parity = 1; -- This will handle, whether the y componet is below the x-axis in the unit-circle or not, so the angle has the right orientation
		if(ai:Normalize().y <0) then
			parity = -1;
		end
		local yaw = math.deg(math.acos(parity*ai.x))+(1-parity)*90;
		e.stargate_movetype = e.stargate_movetype or e:GetMoveType();
		e:SetMoveType(MOVETYPE_NOCLIP); -- Needed, or person dont get teleported correctly
		timer.Simple(0.1,
			function (p)
				p:SetMoveType(p.stargate_movetype or 2);
				p.stargate_movetype = nil;
			end
		,e);
		e:SetPos(p.New);
		e:SetEyeAngles(Angle(pitch,yaw+a.Delta.y+180,0));
		e:SetVelocity(v.New-v.Old);
	end
	if(t == "entity" or t == "npc" or t == "weapon") then
		if(e:GetClass() == "gmod_hoverball") then
			local hp = (p.New-p.Old);
			e.TargetZ = e.TargetZ + hp.z; -- Set changed hoverball heigh to the hoverball
		end
		local ph = e:GetPhysicsObject();
		e:SetPos(p.New);
		if(t == "npc") then a.Delta.p = 0 a.Delta.r = 0 end -- Remove roll and pitch from NPCs
		e:SetAngles(a.Old + a.Delta + Angle(0,180,0));
		e:SetVelocity(-1*v.Old) -- Substract old velocity first!
		if(ph:IsValid()) then
			local ma = ph:GetMass();
			timer.Simple(0.05,ph.ApplyForceCenter,ph,v.New*ma); -- Apply power so it has velocity again
		else
			e:SetVelocity(v.New);
		end
		if(b) then
			for _,v in pairs(b) do
				v.Entity:SetPos(e:LocalToWorld(v.Position));
				v.Entity:SetVelocity(e:LocalToWorld(v.Velocity)-e:GetPos());
			end
		end
	end
	if(t == "vehicle") then
		e:SetAngles(a.New + Angle(0,180,0));
		e:SetPos(p.New);
		for _,v in pairs(b) do
			v.Entity:SetPos(e:LocalToWorld(v.Position));
			v.Entity:SetVelocity(e:LocalToWorld(v.Velocity)-e:GetPos());
		end
	end
end

function GetAllConstrainedEntities( ent, EntTable, ConstraintTable)
    if ( !ent:IsValid() ) then return end
	EntTable[ ent:EntIndex() ] = ent
    if ( !constraint.HasConstraints( ent ) ) then return end
    for key, ConstraintEntity in pairs( ent.Constraints ) do
        if ( !ConstraintTable[ ConstraintEntity ] ) then
            ConstraintTable[ ConstraintEntity ] = true
            if ( ConstraintEntity[ "Ent" ] && ConstraintEntity[ "Ent" ]:IsValid() ) then
                GetAllConstrainedEntities( ConstraintEntity[ "Ent" ].Entity, EntTable, ConstraintTable)
            else
                for i=1, 6 do
                    if ( ConstraintEntity[ "Ent"..i ] && ConstraintEntity[ "Ent"..i ]:IsValid() ) then
                        GetAllConstrainedEntities( ConstraintEntity[ "Ent"..i ].Entity, EntTable, ConstraintTable)
                    end
                end
            end
		end
    end    
	return EntTable, ConstraintTable
end

function math.RotationMatrixWH(axis,angle,vector)
	local a = axis;
	local v = vector;
	local p = math.rad(angle);
	local M = {
		{
			(math.cos(p) + (1-math.cos(p))*math.pow(a.x,2)),
			((1-math.cos(p))*a.x*a.y - math.sin(p)*a.z),
			((1-math.cos(p))*a.x*a.z+math.sin(p)*a.y),
		},
		{
			((1-math.cos(p))*a.y*a.x+math.sin(p)*a.z),
			(math.cos(p) + (1-math.cos(p))*math.pow(a.y,2)),
			((1-math.cos(p))*a.y*a.z - math.sin(p)*a.x),
		},
		{
			((1-math.cos(p))*a.x*a.z - math.sin(p)*a.y),
			((1-math.cos(p))*a.z*a.y+math.sin(p)*a.x),
			(math.cos(p) + (1-math.cos(p))*math.pow(a.z,2)),
		}
	}
	local r = Vector();
	for i=1,3 do
			r[i] = v.x*M[i][1] + v.y*M[i][2] + v.z*M[i][3];
	end
	return r;
end
